#include "git-compat-util.h"
#include "hex-ll.h"
#include "strbuf.h"
#include "url.h"

/**
 * Checks if the given character is valid in a URL scheme.
 *
 * @param first_flag Flag indicating if this is the first character of the
 * scheme.
 * @param ch Character to check.
 * @return Non-zero if the character is valid, zero otherwise.
 */
int is_urlschemechar(int first_flag, int ch)
{
	/*
	 * The set of valid URL schemes, as per STD66 (RFC3986) is
	 * '[A-Za-z][A-Za-z0-9+.-]*'. But use slightly looser check
	 * of '[A-Za-z0-9][A-Za-z0-9+.-]*' because earlier version
	 * of check used '[A-Za-z0-9]+' so not to break any remote
	 * helpers.
	 */
	int alphanumeric, special;
	alphanumeric = ch > 0 && isalnum(ch);
	special = ch == '+' || ch == '-' || ch == '.';
	return alphanumeric || (!first_flag && special);
}

/**
 * Checks if the given string is a valid URL.
 *
 * @param url String to check.
 * @return Non-zero if url is a valid URL, zero otherwise.
 */
int is_url(const char *url)
{
	/* Is "scheme" part reasonable? */
	if (!url || !is_urlschemechar(1, *url++))
		return 0;
	while (*url && *url != ':') {
		if (!is_urlschemechar(0, *url++))
			return 0;
	}
	/* We've seen "scheme"; we want colon-slash-slash */
	return (url[0] == ':' && url[1] == '/' && url[2] == '/');
}

/**
 * Decodes the percent-encoded characters in a URL query string.
 *
 * @param query Pointer to the query string to decode. Will be updated
 *     to point to the remainder of the string after decoding.
 * @param len Length of the query string.
 * @param stop_at Optional string of characters to stop at.
 * @param out String buffer to append the decoded query string to.
 * @param decode_plus Whether to decode '+' to space.
 * @return The decoded query string detached from the output buffer.
 */
static char *url_decode_internal(const char **query, int len,
				 const char *stop_at, struct strbuf *out,
				 int decode_plus)
{
	const char *q = *query;

	while (len) {
		unsigned char c = *q;

		if (!c)
			break;
		if (stop_at && strchr(stop_at, c)) {
			q++;
			len--;
			break;
		}

		if (c == '%' && (len < 0 || len >= 3)) {
			int val = hex2chr(q + 1);
			if (0 < val) {
				strbuf_addch(out, val);
				q += 3;
				len -= 3;
				continue;
			}
		}

		if (decode_plus && c == '+')
			strbuf_addch(out, ' ');
		else
			strbuf_addch(out, c);
		q++;
		len--;
	}
	*query = q;
	return strbuf_detach(out, NULL);
}

/**
 * Decodes the percent-encoded characters in the given URL.
 *
 * @param url The percent-encoded URL to decode.
 * @return A newly allocated string containing the decoded URL.
 */
char *url_decode(const char *url)
{
	return url_decode_mem(url, strlen(url));
}

/**
 * Decodes the percent-encoded characters in the given URL segment,
 * up to the specified length.
 *
 * @param url The percent-encoded URL segment to decode.
 * @param len The maximum number of characters to decode.
 * @return A newly allocated string containing the decoded URL segment.
 */
char *url_decode_mem(const char *url, int len)
{
	struct strbuf out = STRBUF_INIT;
	const char *colon = memchr(url, ':', len);

	/* Skip protocol part if present */
	if (colon && url < colon) {
		strbuf_add(&out, url, colon - url);
		len -= colon - url;
		url = colon;
	}
	return url_decode_internal(&url, len, NULL, &out, 0);
}

/**
 * Decodes the percent-encoded characters in a URL segment.
 *
 * @param encoded The percent-encoded URL segment to decode.
 * @return A newly allocated string containing the decoded URL segment.
 */
char *url_percent_decode(const char *encoded)
{
	struct strbuf out = STRBUF_INIT;
	return url_decode_internal(&encoded, strlen(encoded), NULL, &out, 0);
}

/**
 * Decodes the name part of a URL parameter from the given query string.
 * Advances the query string pointer past the decoded name.
 */
char *url_decode_parameter_name(const char **query)
{
	struct strbuf out = STRBUF_INIT;
	return url_decode_internal(query, -1, "&=", &out, 1);
}

/**
 * Decodes the value part of a URL parameter from the given query string.
 * Advances the query string pointer past the decoded value.
 */
char *url_decode_parameter_value(const char **query)
{
	struct strbuf out = STRBUF_INIT;
	return url_decode_internal(query, -1, "&", &out, 1);
}

/**
 * Appends a '/' to the end of the given URL if it does not already end with a
 * '/'. Updates the destination buffer to contain the URL with trailing '/'.
 *
 * @param buf Pointer to the destination buffer to update with the URL with
 * trailing '/'.
 * @param url The URL to append '/' to.
 */
void end_url_with_slash(struct strbuf *buf, const char *url)
{
	strbuf_addstr(buf, url);
	strbuf_complete(buf, '/');
}

/**
 * Appends a '/' to the end of the given URL if it does not already end with a
 * '/'. Updates the destination string to contain the URL with trailing '/'.
 *
 * @param url The URL to append '/' to.
 * @param dest Pointer to the destination string to update with the URL with
 * trailing '/'.
 */
void str_end_url_with_slash(const char *url, char **dest)
{
	struct strbuf buf = STRBUF_INIT;
	end_url_with_slash(&buf, url);
	free(*dest);
	*dest = strbuf_detach(&buf, NULL);
}
